﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="NVelocity.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.Saf.Actions.Templating
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;

    

    using Commons.Collections;

    using Engine;

    using Entities;

    using global::NVelocity;
    using global::NVelocity.App;
    using global::NVelocity.Context;

    using SharePoint.Wss.Attributes;

    using Utils;

    /// <summary>
    /// The get files.
    /// </summary>
    [CollaborisClass(
          Title = "NVelocity Template Generator",
          ReleaseStatus = ReleaseStatus.Beta,
          DisableDocumentation = false,
          ContactAuthorEmail = "Support@Collaboris.co.uk",
          Summary = "This action requires a user defined collection and the name of " +
                    "an NVelocity Template, to create an instance of an outputted document. ",
          Remarks = @"Please read about NVelocity on the <a href=""http://www.castleproject.org/others/nvelocity/index.html"">Castle Project</a>",
          ClassGuid = "{156E17A5-A7BA-4e1f-8A48-1BC341A7C623}",
          SupportFiles =  new string[] {@"Actions\Templating\HelloWorld.vm"} 
      )
    ]
    public class NVelocity : ActionBase<NVelocityInfo>
    {
        /// <summary>
        /// Executes the Action using the values passed in by the Data Entity
        /// and the <see cref="SafContext"/>.
        /// </summary>
        /// <param name="context">A valid <see cref="SafContext"/> containing properties to run the Action with.</param>
        /// <param name="entity">A Valid <see cref="T"/> containing the parameters with which to run the Action with.</param>
        /// <returns>
        /// A <see cref="ProcessStatus"/> signifying the outcome of processing the Action.
        /// </returns>
        protected override ProcessStatus DoAction(SafContext context, NVelocityInfo entity)
        {
            VelocityContext velocityContext = this.GetVelocityContext(entity, context);

            if (!string.IsNullOrEmpty(entity.TemplateFile))
            {
                if (!File.Exists(entity.TemplateFile))
                {
                    throw new FileNotFoundException(entity.TemplateFile);
                }

                entity.OutputText = Render(entity.TemplateFile, velocityContext);

                if (string.IsNullOrEmpty(entity.OutputText))
                {
                    return ProcessStatus.Warning;
                }

                if (string.IsNullOrEmpty(entity.OutputFile))
                    return ProcessStatus.Success;

                // ok so we need to also create the string as a file...
                if (File.Exists(entity.OutputFile))
                {
                    if (entity.ReplaceOutputFile == false)
                    {
                        TraceHelper.TraceInfo(
                                TraceSwitch,
                                "NVelocity",
                                "Unable to Create file '{0}' as it already exits. Consider setting 'ReplaceOutputFile' to true.",
                                entity.OutputFile);

                        return ProcessStatus.Warning;
                    }

                    File.Delete(entity.OutputFile);
                }

                using (FileStream fs = File.Create(entity.OutputFile))
                {
                    Byte[] info = new UTF8Encoding(true).GetBytes(entity.OutputText);
                    

                    TraceHelper.TraceVerbose(TraceSwitch, "NVelocity", "Creating this '{0}' file with NVelocity", entity.OutputFile);

                    // Add the merged templated...
                    fs.Write(info, 0, info.Length);
                }
            }

            return ProcessStatus.Success;
        }

        /// <summary>
        /// Checks the Data entity used by the Action for Validity
        /// </summary>
        /// <param name="context">The <see cref="SafContext"/> passed in by the SAF Engine.</param>
        /// <param name="entity">The data entity to Validate.</param>
        /// <param name="errors">List of <see cref="ValidationError"/>s</param>
        /// <param name="mode"><see cref="ProcessMode"/>signalling wheter we are Doing, or Undoing the action.</param>
        /// <returns>false if invalid; otherwise true.</returns>
        protected override bool IsValid(SafContext context, NVelocityInfo entity, 
            ref System.Collections.Generic.List<ValidationError> errors, ProcessMode mode)
        {
            int currentCount = errors.Count;

            ValidateStringForNull(entity.TemplateFile, "NVelocityInfo.TemplateFile", ref errors);
            
            if (!entity.IncludeSafProperties.HasValue)
                entity.IncludeSafProperties = true;
            
            if (!entity.ReplaceOutputFile.HasValue)
                entity.ReplaceOutputFile = true;
            
            // Have we added errors ?
            return errors.Count == currentCount;
        }

        /// <summary>
        /// Gets the velocity context.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="context">The context.</param>
        /// <returns></returns>
        private VelocityContext GetVelocityContext(NVelocityInfo entity, SafContext context)
        {
            VelocityContext velocityContext = new VelocityContext();

            if (entity.IncludeSafProperties.HasValue)
            {
                if (entity.IncludeSafProperties.Value)
                {
                    foreach (string key in context.ActionProperties.Keys)
                    {
                        velocityContext.Put(key, context.ActionProperties[key]);
                    }
                }
            }

            // add the Properties passed in
            foreach (string key in entity.Properties.Keys)
            {
                velocityContext.Put(key, entity.Properties[key]);  
            }

            return velocityContext;
        }

        /// <SUMMARY>   
        /// Render a template with the Velocity engine   
        /// </SUMMARY>   
        private static string Render(string templateFile, IContext context)
        {
            VelocityEngine velocityEngine = new VelocityEngine();
            
            velocityEngine.AddProperty("file.resource.loader.class", "NVelocity.Runtime.Resource.Loader.FileResourceLoader, NVelocity, Version=1.0.3.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc");
            velocityEngine.AddProperty("directive.manager", "Collaboris.Saf.Actions.Templating.DirectiveManager;Collaboris.Saf; Version=2.0.0.0; Culture=neutral; PublicKeyToken=182db3eac6a9e195");
            velocityEngine.AddProperty("resource.manager.class", "NVelocity.Runtime.Resource.ResourceManagerImpl, NVelocity, Version=1.0.3.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc");
            velocityEngine.AddProperty("resource.manager.cache.class", "NVelocity.Runtime.Resource.ResourceCacheImpl, NVelocity, Version=1.0.3.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc");

            velocityEngine.AddProperty("runtime.introspector.uberspect", "NVelocity.Util.Introspection.UberspectImpl, NVelocity, Version=1.0.3.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc");
            velocityEngine.AddProperty("directive.evaluate.context.class", "NVelocity.VelocityContext, NVelocity, Version=1.0.3.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc");
            velocityEngine.AddProperty("parser.pool.class", "NVelocity.Runtime.ParserPoolImpl, NVelocity, Version=1.0.3.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc");
            
            velocityEngine.Init();
            
            using (StringWriter writer = new StringWriter())
            {
                using (StreamReader sr = new StreamReader(templateFile))
                    velocityEngine.Evaluate(context, writer, "Collaboris.Actions.Templating.NVelocity", sr);

                return writer.GetStringBuilder().ToString();
            }
        }

        /// <summary>
        /// This is to overcome an issue with the current NVelocity library, it seems the 
        /// default runetime properties/directives (nvelocity.properties and directive.properties
        /// files) are not being properly located in  the library at load time. A jira should 
        /// be filed but for now we attempt to do this on our own. Particularly our 
        /// concern here is with several required properties which I don't want 
        /// to require users to re-defined. e.g.,:
        /// <br/>
        /// 
        /// Pre-requisites:<br/>
        /// resource.manager.class=NVelocity.Runtime.Resource.ResourceManagerImpl <br/>
        /// directive.manager=NVelocity.Runtime.Directive.DirectiveManager <br/>
        /// runtime.introspector.uberspect=NVelocity.Util.Introspection.UberspectImpl <br/>
        /// </summary>
        //private static void LoadDefaultProperties(ExtendedProperties extendedProperties, VelocityEngine nVelocity)
        //{
        //    //IResource defaultRuntimeProperties = new AssemblyResource("assembly://NVelocity/NVelocity.Runtime.Defaults/nvelocity.properties");
        //    //IResource defaultRuntimeDirectives = new AssemblyResource("assembly://NVelocity/NVelocity.Runtime.Defaults/directive.properties");
           
        //    Stream defaultRuntimeProperties = nVelocity.GetType().AssemblyName.GetManifestResourceStream("NVelocity.Runtime.Defaults.nvelocity.properties");
        //    Stream defaultRuntimeDirectives = nVelocity.GetType().AssemblyName.GetManifestResourceStream("NVelocity.Runtime.Defaults.directive.properties");

        //    FillProperties(extendedProperties, defaultRuntimeProperties, false);
        //    FillProperties(extendedProperties, defaultRuntimeDirectives, false);
        //}

        /// <summary>
        /// Populates the velocity properties from the given resource
        /// </summary>
        /// <param name="extendedProperties">ExtendedProperties instance to populate</param>
        /// <param name="resource">The resource from which to load the properties</param>
        /// <param name="append">A flag indicated weather the properties loaded from the resource should be appended or replaced in the extendedProperties</param>
        private static void FillProperties(ExtendedProperties extendedProperties, Stream resource, bool append)
        {
            try
            {
                if (append)
                {
                    extendedProperties.Load(resource);
                }
                else
                {
                    ExtendedProperties overrides = new ExtendedProperties();
                    overrides.Load(resource);
                    foreach (DictionaryEntry entry in overrides)
                    {
                        extendedProperties.SetProperty(Convert.ToString(entry.Key), entry.Value);
                    }
                }
            }
            finally
            {
                resource.Close();
            }
        }
    }
}